import matplotlib.pyplot as plt
import numpy as np

plt.rcParams['font.sans-serif'] = ['SimHei']  # 步骤一（替换sans-serif字体）
plt.rcParams['axes.unicode_minus'] = False  # 步骤二（解决坐标轴负数的负号显示问题）

# 遗传算法类
class GA:
    def __init__(self):
        self.M = None
        # 解码值
        self.x = None
        self.y = None
        # 编码值
        self.X = None
        self.Y = None
        # 交叉概率
        self.Pc = 0.5
        # 变异概率
        self.Pm = 0.005
        # 迭代次数
        self.iteration_num = 200
        # 保存最好的结果
        self.Best = [0]
    # 目标函数
    def Camel(self, x, y):
        camel = (4 - 2.1 * x ** 2 + x ** 4 / 3) * x ** 2 + x * y + (
                -4 + 4 * y ** 2) * y ** 2
        return camel

    # 随机生成一个种群
    def random_population(self):
        M = np.random.randint(2, size=(200, 20))
        return M

    # 编码、解码转换
    def transition(self, M):

        x = np.zeros(200)
        y = np.zeros(200)

        # 输入编码值矩阵
        self.X = M[:, 0:10]
        self.Y = M[:, 10:20]
        # 解码计算
        for i in range(len(M)):
            XXt = 0
            YYt = 0
            for j in range(10):
                XXt += self.X[i, j] * 2 ** (-(j - 9))
                YYt += self.Y[i, j] * 2 ** (-(j - 9))

            x[i] = 6 * XXt / (1024 - 1) - 3
            y[i] = 4 * YYt / (1024 - 1) - 2
        # 返回解码值
        return x, y

    def transition_min(self, M):
        # 输入编码值矩阵
        X = M[0:10]
        Y = M[10:20]
        # 解码计算
        XXt = 0
        YYt = 0
        for j in range(10):
            XXt += X[j] * 2 ** (-(j - 9))
            YYt += Y[j] * 2 ** (-(j - 9))

        x = 6 * XXt / (1024 - 1) - 3
        y = 4 * YYt / (1024 - 1) - 2
        # 返回解码值
        return x, y

    # 求解适应度
    def fitness(self, M):
        # 得到随机点坐标(x , y)
        x, y = self.transition(M)
        # 计算函数值
        fx = self.Camel(x, y)
        # 取出数组中的最大值
        Cmax = np.max(fx)
        # 获得适应度函数
        fitness = Cmax - fx
        # 返回适应度值、函数值
        return fitness, fx

    # 选择算子
    # def select(self, M):
    #     # 获得适应度值、函数值
    #     fit, fx = self.fitness(M)
    #     dominant = []
    #     num = np.argsort(fx)
    #     index_num = num[:100]
    #     for i in index_num:
    #         dominant.append(M[i])
    #     dominant = np.asarray(dominant)
    #     return dominant
        # 计算被选择选择概率
    # 选择算子
    def select(self, M):
        fit, fx = self.fitness(M)
        select_Prob = fit / np.sum(fit)
        dominant = []
        for j in range(100):
            m = 0
            r = np.random.rand()
            for i in range(len(select_Prob)):
                m += select_Prob[i]
                if (m > r):
                    dominant.append(M[i])
                    break
        return dominant

    # 交叉算子 输入：优势群体
    def cross(self, M):
        # 用来保存杂交个体
        new_M = []
        for i in range(100):
            # 遍历优势个体的每一个个体，将其作为父亲
            k1 = np.random.randint(0, 200)
            k2 = np.random.randint(0, 200)
            father = M[k1, :]  # 选择其中一个优势个体
            while (k1 == k2):
                k2 = np.random.randint(0, 200)
            if (k1 != k2):
                # 随机产生一个母亲
                mather = M[k2, :]
                # 随机产生一个交叉点
                cross_point = np.random.randint(0, 20)
                # 基因交叉
                father[cross_point:] = mather[cross_point:]
                # 基因变异
                child = self.mutation(father)
                new_M.append(child)
        # 将列表转化为数组元素
        new_M = np.asarray(new_M)
        # 返回新种群
        return new_M

    # 变异算子
    def mutation(self, M):
        r = np.random.rand()
        # 当大于变异概率时，发生基因突变
        if (r >= self.Pm):
            i = np.random.randint(0, 20)
            M[i] = ~ M[i].astype(np.bool_)  # 取反操作
        return M

    # 迭代函数
    def iteration(self):
        M = self.random_population()  # 随机生成种群矩阵
        fmin = np.zeros(self.iteration_num)  # 保存最小值结果
        for i in range(self.iteration_num):
            dominant = self.select(M)  # 挑选出 100 优势个体
            the_other_dominant = self.cross(M)  # 用这50个优势个体杂交出 100个体
            M[0:100, :] = dominant
            M[100:200, :] = the_other_dominant
            # 产生新优势种群
            fit, fx = self.fitness(M)  # 计算适应度
            fmin[i] = np.min(fx)  # 取得最小的值
            fx_min_index = np.argmin(fx)  # 获取最小值的索引值
            M1 = M[fx_min_index]  # 得到最小值的编码点
            x, y = self.transition_min(M1)
            f = self.Camel(x, y)
            if f <= np.min(self.Best):
                self.Best.append(f)
                x, y = self.transition_min(M1)
            # 保存本次最佳结果
            else:
                self.Best.append(np.min(self.Best))
            # if f <= np.min(self.Best):
            #     return  x, y, f
            # else:
        print('最优点x:',x,'最优点y:',y,'最优值:',np.min(self.Best))
        return x, y, self.Best[1:]



a = GA()
x, y, f = a.iteration()
fig = plt.figure()
plt.xlabel('迭代次数')
plt.ylabel('最优函数值')
plt.plot(np.linspace(0, a.iteration_num, a.iteration_num), f, color="black", label='函数值')
plt.legend()

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
xm = np.linspace(-3, 3, 100)
ym = np.linspace(-2, 2, 100)
xm, ym = np.meshgrid(xm, ym)
zm = a.Camel(x=xm, y=xm)
ax.scatter(x, y, f, color='r', s=20)
ax.plot_wireframe(xm, ym, zm, rstride=5, cstride=5, cmap=plt.cm.coolwarm)

plt.show()
# print( fx ,fx_min_index)
